// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: leon

#pragma once

#include <string>

#include <rapidjson/document.h>
#include <rapidjson/rapidjson.h>

#include "coin/proto/coin_market_enums.pb.h"
#include "coin2/base/conversion.h"
#include "coin2/base/time.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/book_builder.h"
#include "coin2/exchange/base/feed_util/trade.h"

namespace impl {
using coin2::exchange::base::market::ExchangeType;
using coin2::exchange::base::market::MarketType;

class PhemexBaseFeedUpdateParser {
 public:
  static bool ParseSnapshot(
      BookBuilder* book_builder_,
      int64_t ts,
      const rapidjson::Value* book_json,
      double price_multiplier,
      double qty_multiplier) {
    auto tx = book_builder_->NewTransaction(ts);
    tx.Clear();
    for (const auto& tick : (*book_json)["asks"].GetArray()) {
      tx.UpdateAsk(stod(tick[0].GetString()) * price_multiplier,
                   stod(tick[1].GetString()) * qty_multiplier);
    }
    for (const auto& tick : (*book_json)["bids"].GetArray()) {
      tx.UpdateBid(stod(tick[0].GetString()) * price_multiplier,
                   stod(tick[1].GetString()) * qty_multiplier);
    }
    return true;
  }

  static bool ParseIncremental(
      BookBuilder* book_builder_,
      int64_t ts,
      const rapidjson::Value* book_json,
      double price_multiplier,
      double qty_multiplier) {
    auto tx = book_builder_->NewTransaction(ts);
    int update_cnt = 0;
    for (const auto& tick : (*book_json)["asks"].GetArray()) {
      tx.UpdateAsk(stod(tick[0].GetString()) * price_multiplier,
                   stod(tick[1].GetString()) * qty_multiplier);
      ++update_cnt;
    }
    for (const auto& tick : (*book_json)["bids"].GetArray()) {
      tx.UpdateBid(stod(tick[0].GetString()) * price_multiplier,
                   stod(tick[1].GetString()) * qty_multiplier);
      ++update_cnt;
    }
    return update_cnt > 0;
  }

  static void ParseTrade(
      const rapidjson::Value& trade_json,
      Trade* trade,
      double price_multiplier,
      double qty_multiplier) {
    trade->timestamp = stoll(trade_json[0].GetString());
    trade->side = ParseTradeSide(trade_json[1].GetString());
    trade->price = stod(trade_json[2].GetString()) * price_multiplier;
    trade->fill_qty = stod(trade_json[3].GetString()) * qty_multiplier;
  }

  static bool Parse1Kline(
      const rapidjson::Value& kline_data,
      Klines* klines,
      int64_t timestamp,
      fastfeed::proto::KlineInterval interval,
      double price_multiplier,
      double qty_multiplier) {
    CHECK((kline_data.Size() >= 9));
    int64_t open_timestamp = stoll(kline_data[0].GetString()) * 1'000'000'000L;
    auto result = klines->AddKline(open_timestamp);
    if (!result) return false;

    Kline* kline;
    bool freezed;
    std::tie(kline, freezed) = result.value();

    kline->open = stod(kline_data[3].GetString()) * price_multiplier;
    kline->high = stod(kline_data[4].GetString()) * price_multiplier;
    kline->low = stod(kline_data[5].GetString()) * price_multiplier;
    kline->close = stod(kline_data[6].GetString()) * price_multiplier;
    kline->volume = stod(kline_data[7].GetString()) * qty_multiplier;
    kline->turnover = stod(kline_data[8].GetString()) * price_multiplier;

    kline->timestamp = timestamp;
    kline->interval = interval;
    return freezed;
  }


  static fastfeed::proto::KlineInterval ParseKlineInterval(const std::string& period) {
    static std::map<std::string, fastfeed::proto::KlineInterval> interval_map = {
      {"60", fastfeed::proto::KLINE_INTERVAL_1MIN},
      {"300", fastfeed::proto::KLINE_INTERVAL_5MIN},
      {"900", fastfeed::proto::KLINE_INTERVAL_15MIN},
      {"1800", fastfeed::proto::KLINE_INTERVAL_30MIN},
      {"3600", fastfeed::proto::KLINE_INTERVAL_1HOUR},
      {"86400", fastfeed::proto::KLINE_INTERVAL_1DAY},
      {"604800", fastfeed::proto::KLINE_INTERVAL_1WEEK},
      {"2592000", fastfeed::proto::KLINE_INTERVAL_1MON}
    };
    auto iter = interval_map.find(period);
    if (iter == interval_map.end()) {
      return fastfeed::proto::KLINE_INTERVAL_INVALID;
    }

    return iter->second;
  }

 private:
  static coin2::exchange::feed::TradeSide ParseTradeSide(const std::string& side) {
    if (side == "Buy") {
      return coin2::exchange::feed::TRADE_BUY_SIDE;
    } else if (side == "Sell") {
      return coin2::exchange::feed::TRADE_SELL_SIDE;
    } else {
      throw std::runtime_error("[Phemex Feed Processor] unknown side: " + side);
    }
  }
};
}  // namespace impl

using impl::PhemexBaseFeedUpdateParser;
